/*
 * This file contains the main function and the server part of the program
 */
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h> 
#include <pthread.h>
#include "Player_manager.c"
#include "Authentication_manager.c"

#define LISTEN_PORT 54321
//#define TALK_PORT 54322
#define MAXPENDING 30
#define MAXQUERYSIZE 100
#define DEBUG 0
struct threadQueue
{
	int thread_id[MAXPENDING];
	int current_size;
	int head;
};
struct threadQueue thread_queue;

void *work(void *arg);
void enqueue(int id);
int dequeue();
char* strsimplify(char*strin);


int listen_sock, client_sock; 
struct sockaddr_in my_address;    /* my address information */
struct sockaddr_in their_address; /* connector's address information */
int sin_size;

pthread_mutex_t listen_mutex, queue_mutex, stats_mutex;
pthread_cond_t idle[MAXPENDING];
pthread_t work_thread[MAXPENDING];

int main()
{
	int current_thread;
	thread_queue.head = 0;
	pthread_mutex_init(&listen_mutex,NULL);
	pthread_mutex_init(&queue_mutex,NULL);
	pthread_mutex_init(&stats_mutex,NULL);
	int x;
	for(x = 0;x <MAXPENDING;x++)
	{
		enqueue(x);
		if(pthread_create(&work_thread[x], NULL, work,(void *)x))
		{
			printf("error creating thread");
			abort();
		}
	}
	// read player data
	init();
	
	listen_sock = socket(AF_INET,SOCK_STREAM,0);
	if(listen_sock == -1)
	{
		perror("socket fail");
		exit(1);
	}
	
	my_address.sin_family = PF_INET;
	my_address.sin_port = htons(LISTEN_PORT);
	my_address.sin_addr.s_addr = INADDR_ANY;
	memset(&(my_address.sin_zero),0,8);
	
	if(bind(listen_sock, (struct sockaddr *)&my_address,sizeof(struct 			sockaddr)) == -1)
	{
		perror("bind() error");
		exit(1);
	}
	if(listen(listen_sock,30) == -1)
	{
		perror("listen() fail");
	}
	sin_size = sizeof(struct sockaddr);
	printf("Server Ready\n");
	while(1)
	{
		client_sock = accept(listen_sock, (struct sockaddr *) &their_address, &sin_size);
		if(client_sock == -1)
		{
			perror("accept() fail");
			exit(1);
		}
		printf("server: Connection from %s\n",
				(char*)inet_ntoa		(their_address.sin_addr));
		current_thread = dequeue();
		//printf("signaling thread %d\n",current_thread);
		pthread_mutex_unlock(&listen_mutex);
		pthread_cond_signal(&idle[current_thread]);
	}
	
	return 0;	
}
void *work(void *arg)
{

	int id = (int)arg;
	//char query[MAXQUERYSIZE];
	
	while(1)
	{
		//printf("thread %d ready\n",id);
		pthread_mutex_lock(&listen_mutex);
		pthread_cond_wait(&idle[id],&listen_mutex);
		int work_client_sock = client_sock;
		pthread_mutex_unlock(&listen_mutex);//this may need to be further down
		printf("waiting for authorization\n");
		char user_name[100];
		int name_size;
		// authenticate
		name_size = recv(client_sock ,user_name,100,0);
		if (name_size == -1)
		{
			printf("recv() fail");
			exit(1);
			continue;
		}
		if(DEBUG){
			printf("DEBUG: user name bytes in:%d\n",name_size);
		}
		//user_name[name_size] = '\0';
		;
		char password_in[100];
		int pass_size;
		pass_size = recv(client_sock , password_in,100,0);
		if (pass_size == -1)
		{
			printf("recv() fail");
			enqueue(id);
			close(work_client_sock);
			continue;
		}
		char * password = strsimplify(password_in);
		char * user = strsimplify(user_name);
		if(DEBUG){
			printf("DEBUG: password bytes in:%d\n",pass_size);
			printf("DEBUG: password: %s\n",password);
		}

		
		int authorized = authenticate(user,password);

		//int authorized = 1;
		if(DEBUG)printf("auth: %d\n",authorized);
		if(send(work_client_sock,&authorized,sizeof(int),0) != sizeof(int))
		{
			printf("send faild\n");
			enqueue(id);
			close(work_client_sock);
			continue;
		}
		if(!authorized)
		{
			printf("user details incorrect\n");
			printf("closing connection\n");
			enqueue(id);
			close(work_client_sock);
			continue;
		}
		printf("Authorized user: %s\n",user);
		//free dynamic memory
		free(password);
		free(user);
		//get query
		char query_buff[100];
		int query_size;
		query_size = recv(client_sock ,query_buff,100,0);
		if (query_size == -1)
		{
			printf("recv() fail\n");
			enqueue(id);
			close(work_client_sock);
			continue;
			
		}
		if(query_size == 0)
		{
			printf("zero size query\n");
			enqueue(id);
			close(work_client_sock);
			continue;
		}
		if(DEBUG){
			printf("DEBUG: query bytes in:%d\n",query_size);
		}
		//send result
		char * query = strsimplify(query_buff);
		if(DEBUG) printf("debug:%s\n",query);
		struct cricket_player p;
		//query_buff[strlen(query_buff)] = '\0';
		int success = 0;
		pthread_mutex_lock(&stats_mutex);
		success = get_player(query,&p);
		pthread_mutex_unlock(&stats_mutex);
		free(query);
		if(send(work_client_sock,&success,sizeof(int),0) !=sizeof(int))
		{
			printf("send fail\n");
			enqueue(id);
			close(work_client_sock);
			continue;
		}
		if(DEBUG)printf("success%d :\n",success);
		if(!success)
		{
			printf("Query not in database\n Work done\n");
			enqueue(id);
			close(work_client_sock);
			continue;
		}
		
		if(DEBUG)printf("Player name: %s\n",p.name);
		if(send(work_client_sock, &p,sizeof(p),0) !=sizeof(p))
		{
			printf("send faild\n");
			enqueue(id);
			close(work_client_sock);
			continue;
		}
		printf("\"%s\" has been queried %d times\n",p.name,p.access_count);
		/*if(send(work_client_sock, "hard coded msg\n",15,0) !=15)
		{
			printf("send faild\n");
			enqueue(id);
			close(work_client_sock);
			continue;
		}*/
		printf("Query finished\n",id);
		enqueue(id);
		close(work_client_sock);
		
	}
}

void enqueue(int id)
{
	pthread_mutex_lock(&queue_mutex);
	thread_queue.thread_id[(thread_queue.head+thread_queue.current_size)%MAXPENDING] = id;
	thread_queue.current_size++;
	pthread_mutex_unlock(&queue_mutex);
}
int dequeue()
{
	pthread_mutex_lock(&queue_mutex);
	int item = thread_queue.thread_id[thread_queue.head];
	thread_queue.current_size--;
	thread_queue.head = (thread_queue.head+1)%MAXPENDING;
	pthread_mutex_unlock(&queue_mutex);
	return item;
}

char* strsimplify(char*strin)
{
	if(DEBUG)printf("simplifing %s\n",strin);
	int len = strlen(strin);
	char * temp = (char*)malloc(len);
	char * chptr;
	chptr = strtok(strin," \n\t");
	memcpy(temp,chptr,len);
	if(DEBUG)printf("simplified to:%s\n",temp);
	return temp;
}
